/**
 ******************************************************************************
 * @file	bl_sleep.c
 * @brief	LCD背光调节


 * 5秒没有输入调低屏幕亮度，10 秒没有输入则关闭屏幕，
 * 控制台输入
 * ./bl_sleep.elf -t 10 -D /dev/input/event0


- 2016-9-30,MenglongWu,MenglongWoo@aliyun.com
 	- brief

*/


#include <linux/input.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include "proginfo.h"
#include "autoconfig.h"
#include "printl.h"


#if defined(CONFIG_DBG_BL_POWER)
	#define dbg(fmt, ...) PRINTL(1, C_DEBUG  fmt C_NORMAL , ##__VA_ARGS__)
#else
	#define dbg(fmt, ...)
#endif


/*
 * Linux Kernel 对定时器的应用宏
 */
#define typecheck(type,x) \
({        type __dummy; \
        typeof(x) __dummy2; \
        (void)(&__dummy == &__dummy2); \
        1; \
})

#define time_after(a,b) \
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)






/*
 * 不要将MIN_LEVEL调成0，否则及时当背光打开时用户也看不到屏幕内容，
 * 误以为设备损坏，保证最小亮度也是能看到屏幕内容的
*/
#define MIN_LEVEL       (10)				// 可调节最暗亮度
#define AFTER_N_SEC	(5)				// 最后n秒调暗背光
#define LOW_POWER_LEVEL (40)


#define DEF_DEVICE_GPIOKEY    "/dev/input/event0"
#define DEF_DEVICE_TOUCH      "/dev/input/event2"
#define DEF_DEVICE_BACKLIGHT  "/sys/class/backlight/mxs-bl/"
#define DEV_DEVICE_POWER      "/sys/class/leds/power/"

char *device   = "/dev/input/event0";			// 默认设备
char *dev_keyboard = DEF_DEVICE_GPIOKEY;			// 默认设备
char *dev_touch    = DEF_DEVICE_TOUCH;			// 默认设备
char *dev_bl = DEF_DEVICE_BACKLIGHT;		// LCD背光电源
char *dev_power = DEV_DEVICE_POWER;		// 电源开关

int timeout   = 30;					// 无操作超时
int level     = 80;					// 关闭前LCD背光亮度


char strout[128];

pthread_mutex_t timeout_mutex;
static pthread_t g_readtimeout;


static void print_usage(const char *prog)
{
	printf("Usage: %s [-DsbdlHOLC3]\n", prog);
	puts("  -D --device   device to use (default /dev/input/event0)\n"
	     "  -p --power    backlight power (default /sys/class/backlight/mxs-bl/)\n"
	     "  -t --timeout  no input event timeout (30 sec)\n");
	exit(1);
}

void load_env()
{
	char *env = NULL;

	env = getenv("DEVICE_GPIOKEY");
	if (env) {
		dev_keyboard = env;
	}
	env = getenv("DEVICE_TOUCH");
	if (env) {
		dev_touch = env;
	}

	env = getenv("DEVICE_BACKLIGHT");
	if (env) {
		dev_bl = env;
	}
	env = getenv("DEVICE_POWER");
	if (env) {
		dev_power = env;
	}
}
static void parse_opts(int argc, char *argv[])
{
	while (1) {
		static const struct option lopts[] = {
			{ "device",  1, 0, 'D' },
			{ "keyboard",  1, 0, 'D' },
			{ "touch",  1, 0, 'D' },
			{ "timeout", 1, 0, 't' },
			{ "dev_bl", 1, 0, 'p' },
			{ NULL, 0, 0, 0 },
		};
		int c;

		c = getopt_long(argc, argv, "D:p:t:", lopts, NULL);

		if (c == -1) {
			break;
		}

		switch (c) {
		case 'D':
			// device = optarg;
			if (strcmp("--keyboard", argv[optind - 2]) == 0) {
				dev_keyboard = optarg;
			} else if (strcmp("--touch", argv[optind - 2]) == 0) {
				dev_touch    = optarg;
			}
			break;
		case 'p':
			dev_bl = optarg;
			break;
		case 't':
			timeout = atoi(optarg);
			if (timeout < 10) {
				timeout = 10;
			} else if (timeout > 5 * 60) {
				timeout = 5 * 60;
			}
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
}


static inline void full_bl_level()
{
	snprintf(strout, sizeof(strout), "echo 80 > " "%s" "/brightness", dev_bl);
	system(strout);
}




#if defined(CONFIG_BL_LOW_POWER_LEVEL)
static inline void save_bl_level()
{
	FILE *fp;
	char strout[128];

	snprintf(strout, sizeof(strout), "cat " "%s" "/brightness", dev_bl);
	fp = popen((char *)strout, "r");
	if (fp == NULL) {
		return;
	}

	fscanf(fp, "%d", &level);
	pclose(fp);
	printf("%s() %d\n", __FUNCTION__, level);
	if (level < MIN_LEVEL) {
		level = 100;
	}
}
static inline void restore_bl_level()
{
	printf("%s() %d\n", __FUNCTION__, level);
	snprintf(strout, sizeof(strout), "echo %d > " "%s" "/brightness",
	         level, dev_bl);
	system(strout);
}

/**
 * @brief	调低背光亮度
 * @remarks	如果但当前背光亮度小于低低功耗背光(LOW_POWER_LEVEL)则不调节
 */

static inline void low_bl_level()
{
	FILE *fp;
	int old_level;

	snprintf(strout, sizeof(strout), "cat " "%s" "/brightness", dev_bl);
	fp = popen((char *)strout, "r");
	if (fp == NULL) {
		return;
	}

	fscanf(fp, "%d", &old_level);
	pclose(fp);
	if (old_level < LOW_POWER_LEVEL) {
		return;
	}


	snprintf(strout, sizeof(strout), "echo 60 > ""%s""/brightness", dev_bl);
	system(strout);
}



#else
static inline void save_bl_level() {}
static inline void restore_bl_level() {}
static inline void low_bl_level() {}
#endif

void _backlight_power(int onoff)
{
	int fd_wo;
	char power;
	static char power_state = -1;

	power = (onoff == 1) ? '0' : '1';
	if (power_state == power) {
		return;
	}
	// printf("power stat change\n");
	power_state = power;

	snprintf(strout, sizeof(strout), "%s/bl_power", dev_bl);
	fd_wo = open(strout, O_WRONLY);
	if (fd_wo == -1) {
		snprintf(strout, sizeof(strout), "open device '%s/bl_power' fail", dev_bl);
		perror(strout);
		return;
	}


	write(fd_wo, &power, 1);
	close(fd_wo);
}
static inline void off_bl_power()
{
	_backlight_power(0);
	dbg("%s()\n", __FUNCTION__);
}

static inline void on_bl_power()
{
	// snprintf(strout, sizeof(strout), "echo 0 > ""%s""/bl_power", dev_bl);
	// system(strout);
	_backlight_power(1);
	dbg("%s()\n", __FUNCTION__);
}

/**
 * @brief	关闭系统总电源
 */

static inline void off_sys_power()
{
#define POWER_ON '0'
#define POWER_OFF '1'

	int fd_wo;
	char power = POWER_OFF;

	snprintf(strout, sizeof(strout), "%s/brightness", dev_power);
	fd_wo = open(strout, O_WRONLY);
	if (fd_wo == -1) {
		snprintf(strout, sizeof(strout), "open device '%s/bl_power' fail", dev_power);
		perror(strout);
		return;
	}
	dbg("write power %c\n", power);


	write(fd_wo, &power, 1);
	close(fd_wo);
}


int fd;

int key_long_press(
    struct input_event event,
    int scancode,
    int limit_press,
    int *tm_key_down)
{
	if (event.code == 0) {
		return 0;
	}

	if (scancode != event.code) {
		return -1;
	}
	switch(event.value) {
	case 1:  // key down
		dbg("down\n");
		*tm_key_down = event.time.tv_sec;
		break;
	case 2:  // key press
		dbg("press\n");
		if (event.time.tv_sec  - *tm_key_down >= limit_press) {
			dbg("long press\n");
			return 1;
		}
		break;
	case 3:  // key up
		break;
	default:
		return 0;
		break;
	}
	return 0;
}
#include <sys/poll.h>
void *ThreadReadTimeout(void *arg)
{
	int  fd_tmout;
	char strout[128];
	int tm, itmp, last_tm;
	char strtm[10];

	int ret;
	struct pollfd fds[1];


	snprintf(strout, sizeof(strout), "%s/timeout", dev_bl);
	fd_tmout = open((char *)strout, O_RDONLY);
	fds[0].fd = fd_tmout;
	fds[0].events  = POLLPRI;
	ret = read(fd_tmout, strtm, 10);
	last_tm = atoi(strtm);
	while(1) {
		lseek(fd_tmout, 0, SEEK_SET);
		ret = read(fd_tmout, strtm, 10);
		if( ret == -1 ) {
			exit(-1);
		}
		itmp = atoi(strtm);
		if (itmp != last_tm) {
			last_tm = itmp;

			pthread_mutex_lock(&timeout_mutex);
			timeout = itmp;
			dbg("last timeout %d\n", timeout);
			pthread_mutex_unlock(&timeout_mutex);

		}
		sleep(1);
	}
	close(fd_tmout);


	// int gpio_id, gpio_fd, ret;
	// struct pollfd fds[1];
	// char buff[33];
	// gpio_fd = open("/sys/class/backlight/mxs-bl/brightness ", O_RDONLY);
	// if( gpio_fd == -1 ) {
	// 	printf("gpio open");
	// }
	// fds[0].fd = gpio_fd;
	// fds[0].events  = POLLPRI;
	// ret = read(gpio_fd, buff, 10);
	// if( ret == -1 ) {
	// 	printf("read");
	// }
	// while(1) {
	// 	ret = poll(fds, 1, -1);
	// 	if( ret == -1 ) {
	// 		printf("poll");
	// 	}
	// 	if( fds[0].revents & POLLPRI) {
	// 		ret = lseek(gpio_fd, 0, SEEK_SET);
	// 		if( ret == -1 ) {
	// 			printf("lseek");
	// 		}
	// 		ret = read(gpio_fd, buff, 10);
	// 		if( ret == -1 ) {
	// 			printf("read");
	// 		}
	// 		/*此时表示已经监听到中断触发了，该干事了*/
	// 		printf("todo \n");
	// 	}
	// }

}
#include <signal.h>
void sig_alarm(int num)
{
	if (timeout != 0) {
		off_bl_power();
	}
}

void while_unempty(int fdinput)
{
	struct input_event input_press;
	int len;
	while(len != -1) {
		len = read(fdinput, &input_press, sizeof(input_press));
	}
}

int  fd_uptime;
char str_uptime[20];
void open_uptime()
{
	
	fd_uptime = open((char *)"/proc/uptime", O_RDONLY);

	// close(fd_uptime);
}

int look_uptime()
{
	int ret, sec;

	lseek(fd_uptime, 0, SEEK_SET);
	ret = read(fd_uptime, str_uptime, sizeof(str_uptime));
	if (ret == -1) {
		perror("look /proc/uptime");
	}
	sec = atoi(str_uptime);
	return sec;
}

// 自动检测 /dev/input 输入，如果30s内没有任何输入关闭LCD背光
// 检测间隔为1s
void *ThreadBlacklight(void *arg)
{
	int touch_down = 0;
	int power_have_off = 0;
	struct input_event input_press;

	int fd_kb = 0, fd_touch = 0;

	int key_power_long_press = 0;
	int tm_down_start = 0;
	int key_backdoor_1 = 0, key_backdoor_2 = 0;
	int tm_k1_start = 0, tm_k2_start = 0;

	fd = open(device, O_RDONLY | O_NONBLOCK);
	if (fd < 0) {
		printf("fail open device %s\n", device);
		return 0;
	}
	fd_kb = open(dev_keyboard, O_RDONLY | O_NONBLOCK);
	if (fd < 0) {
		printf("fail open keyboard %s\n", device);
		return 0;
	}
	fd_touch = open(dev_touch, O_RDONLY | O_NONBLOCK);
	if (fd < 0) {
		printf("fail open touch %s\n", device);
		return 0;
	}


	int len;
	int loop;
	unsigned long end, now;
	int times = 0;
	int ret;


	

	// TODO 用信号代替sleep
	// signal(int signum, sighandler_t handler);
	while(1) {
		pthread_mutex_lock(&timeout_mutex);
		now = look_uptime();
		loop = timeout;
		// 为了避免loop == 0 时候导致bl_power进入死循环，强行占用CPU
		if (loop == 0) {
			loop = 9;
		}
		end = now + loop;
		pthread_mutex_unlock(&timeout_mutex);
		dbg("start %d end %d\n", now, end);

		// 正常亮度 同时检查触屏和按键是否按下
		times = 0;
		while(time_before(now, (end - 5))) {
			now = look_uptime();

			len = read(fd_touch, &input_press, sizeof(input_press));
			if (len != -1 && input_press.code != 0) {
				touch_down = 1;
				times = 0;
				// 保证所有数据都读取完毕

				while_unempty(fd_touch);
				break;
			}
			len = read(fd_kb, &input_press, sizeof(input_press));
			if (len != -1 && input_press.code != 0) {

				touch_down = 1;
				while_unempty(fd_kb);
				break;
			}
			if (len != -1) {
				sleep(1);
				continue;
			}
			times++;
			if (times > 10) {
				sleep(1);
			}

		}

		save_bl_level();
		// 如有按键按下打开背光
		if (touch_down) {
			goto _power_on;
		}

#if 1
		// 调暗屏幕，
		low_bl_level();
		times = 0;
		while(time_before(now, end)) {
			// gettimeofday( &now, NULL );
			now = look_uptime();
			len = read(fd_touch, &input_press, sizeof(input_press));
			if (len != -1 && input_press.code != 0) {
				touch_down = 1;
				times = 0;
				while_unempty(fd_touch);
				break;
			}
			len = read(fd_kb, &input_press, sizeof(input_press));
			if (len != -1 && input_press.code != 0) {
				touch_down = 1;
				times = 0;
				while_unempty(fd_kb);
				break;
			}
			if (len != -1) {
				times = 0;
				continue;
			}
			times++;
			if (times > 10) {
				sleep(1);
			}

		}
#endif



_power_on:
		// 有输入事件则打开背光，否则关闭背光电源
		if (touch_down) {
			on_bl_power();
			restore_bl_level();

			key_power_long_press = key_long_press(
			                           input_press,
			                           KEY_POWER,
			                           2,
			                           &tm_down_start);
			if (key_power_long_press == 1) {
				dbg("%s() %d: key_power_long_press\n", __FUNCTION__, __LINE__);
				off_bl_power();
				dbg("[bl_power.elf] control power down\n");
#if 0
				system("reboot");
#else
				// BUG 解决长按关机键10s后系统再次重启，屏幕白屏
				system("sync");
				sleep(2);
				off_sys_power();
#endif
			}
			key_backdoor_1 = key_long_press(
			                     input_press,
			                     KEY_ESC,
			                     8,
			                     &tm_k1_start);
			// key_backdoor_2 = key_long_press(
			//                      input_press,
			//                      KEY_F2,
			//                      8,
			//                      &tm_k2_start);
			if (key_backdoor_1 == 1) {
				system("/sbin/ifconfig wan0:10 10.0.10.10 netmask 255.255.255.0");
				system("/sbin/ifconfig usb0:10 10.0.10.12 netmask 255.255.255.0");
			}
		} else {
#if defined(CONFIG_INPUT_TIMEOUT_BL_OFF)
			// 当设置超时为0时，不关闭屏幕
			if (timeout != 0) {
				dbg("%s() %d: normal timeout\n", __FUNCTION__, __LINE__);
				off_bl_power();
			}

#endif
		}



		touch_down = 0;
	}
	close(fd);
}

int main(int argc, char *argv[])
{
	load_env();
	parse_opts(argc, argv);

	// 启动后将背光点亮，并保存背光亮度等级
	on_bl_power();
	full_bl_level();
	save_bl_level();

	open_uptime();

	pthread_mutex_init(&timeout_mutex, NULL);
	pthread_create(&g_readtimeout, NULL, ThreadReadTimeout, NULL);
	ThreadBlacklight(NULL);
	pthread_cancel(g_readtimeout);
}

MODULE_PROJECT(PRJ_NAME);
#if  defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
	MODULE_VERSION(PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif
MODULE_BUILD(__DATE__ " " __TIME__);
MODULE_DEPEND("/sys/class/backlight/xxx/timeout");
PROG_GITSHA1(__GIT_SHA1__);
